
package com.lakala.sh.zf.openplatform.common;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 类HttpClientPool.java的实现描述： 合码支付高并发连接池的配制及使用
 * 
 * @author huanglf 2018年12月9日 下午11:09:49
 */
public class HttpClientPool {

    private final static String HTTP_DEFAULT_CONTENT_TYPE  = "application/json";
    private static final String APIGW_HTTP_EXCEPTION_HEAD  = "APIGW_HTTP_EXCEPTION_HEAD_";
    //根据业务使用场景及对端服务器的基本情况，建立支持高并发和高性能的http连接
    private final static int DEFAULT_REQ_TIMEOUT    = 200;       //从池中请求数据时间  0.2秒
    private final static int DEFAULT_CON_TIMEOUT    = 3 * 1000;  //socket连接超时时间 3秒   未通，最多重连3次
    private final static int DEFAULT_SOCKET_TIMEOUT = 10 * 1000; //页面返回的时间必须在30秒内，否则判定服务不可用

    // 通过枚举来实现单例
    private static enum Singleton {
        Client;
        //线程安全Client
        private final CloseableHttpClient   threadSafeClient;
        //池监控
        private final IdleConnectionMonitor monitor;
        //KeepAlive策略 1分钟  一个建链最多保持1分钟
        ConnectionKeepAliveStrategy         myStrategy = new ConnectionKeepAliveStrategy() {
                                                           public long getKeepAliveDuration(HttpResponse response,
                                                                                            HttpContext context) {
                                                               return 1 * 60 * 1000;                              //如果没有约定，则默认定义时长为120s
                                                           }
                                                       };

        //实例
        private Singleton() {
            Registry<ConnectionSocketFactory> socketFactoryRegistry = null;
            try {
                SSLContextBuilder sslBuilder = new SSLContextBuilder();
                sslBuilder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
                SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslBuilder.build());
                // SSLContext sslContext = SSLContext.getInstance("TLS"); 
                /* 配置同时支持 HTTP 和 HTPPS */
                socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslsf)
                        .build();
            } catch (Exception e) {
                System.out.println("创建SSLContextBuilder异常,"+e);
            }
            PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            cm.setMaxTotal(300);
            cm.setDefaultMaxPerRoute(300); //每个系统保持一个连接
            //保持默认的2秒，不要修改，因这个参数与对方有关系
            //cm.setValidateAfterInactivity(10 * 1000);//10秒就验证连接
            //重试策略
            HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {
                public boolean retryRequest(IOException exp, int retryTimes, HttpContext ctx) {
                    if (retryTimes > 3) {
                        return false;
                    }
                    if (exp instanceof UnknownHostException || exp instanceof ConnectTimeoutException) {
                        //return true;
                        return false; //不进行重连
                    }
                    return false;
                }
            };
            //创建一个Buider
            RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(DEFAULT_REQ_TIMEOUT)
                    .setConnectTimeout(DEFAULT_CON_TIMEOUT).setSocketTimeout(DEFAULT_SOCKET_TIMEOUT)
                    .setCookieSpec(CookieSpecs.STANDARD).build();
            HttpClientBuilder builder = HttpClients.custom().setConnectionManager(cm).setKeepAliveStrategy(myStrategy)
                    .setDefaultRequestConfig(requestConfig).setRetryHandler(retryHandler);
            threadSafeClient = builder.build();
            //排除过期的和空闲的
            monitor = new IdleConnectionMonitor(cm);
            Thread monitorThread = new Thread(monitor);
            monitorThread.setDaemon(true);
            monitorThread.start();
        }

        /**
         * @Description 得到一个线程安全的client
         * @return
         */
        public CloseableHttpClient get() {
            return threadSafeClient;
        }

    }

    /**
     * @Description 得到一个线程安全的client
     * @return
     */
    public static CloseableHttpClient getClient() {
        return Singleton.Client.get();
    }

    /**
     * @Description 关闭池
     * @throws InterruptedException
     * @throws IOException
     */
    public static void shutdown() throws InterruptedException, IOException {
        Singleton.Client.monitor.shutdown();
    }

    //池监控线程
    private static class IdleConnectionMonitor implements Runnable {
        private final PoolingHttpClientConnectionManager cm;
        //使用BlockingQueue来处理停服务事件
        private final BlockingQueue<Stop>                stopSignal = new ArrayBlockingQueue<Stop>(1);

        IdleConnectionMonitor(PoolingHttpClientConnectionManager cm) {
            this.cm = cm;
        }

        public void run() {
            try {
                Stop stopRequest;
                //每3秒check
                while ((stopRequest = stopSignal.poll(3, TimeUnit.SECONDS)) == null) {
                    cm.closeExpiredConnections();//关闭过期的
                    cm.closeIdleConnections(10, TimeUnit.SECONDS);//10秒还一直空闲 就关闭连接
                }
                stopRequest.stopped();
            } catch (InterruptedException ex) {
                System.out.println("中断异常,"+ex);
            }
        }

        //判断池是否关闭
        private static class Stop {
            private final BlockingQueue<Stop> stop = new ArrayBlockingQueue<Stop>(1);

            //通知池关闭
            public void stopped() {
                stop.add(this);
            }

            //取到Stop
            public void waitForStopped() throws InterruptedException {
                stop.take();
            }
        }

        /**
         * @Description 通知池进行关闭
         * @throws InterruptedException
         * @throws IOException
         */
        public void shutdown() throws InterruptedException, IOException {
            Stop stop = new Stop();
            stopSignal.add(stop);
            stop.waitForStopped();
            HttpClientPool.getClient().close();
            cm.close();
        }
    }

    /**
     * @Description POST 请求
     * @param url
     * @param postData
     * @param header
     * @param encoding
     * @return
     * @throws Exception
     */
    public static String doJsonRequest(String url, String postData, Map<String, String> header, String encoding)
            throws Exception {
        //HTTP
        HttpPost postRequest = new HttpPost(url.trim());
        //BODY
        StringEntity stringEntity = new StringEntity(postData, encoding);
        stringEntity.setContentType(HTTP_DEFAULT_CONTENT_TYPE);
        postRequest.setEntity(stringEntity);
        return getHttpResultMsg(url, header, postRequest, encoding);
    }

    /**
     * @Description GET请求
     * @param url
     * @param header
     * @param encoding
     * @return
     * @throws Exception
     */
    public static String doGetRequest(String url, Map<String, String> header, String encoding) throws Exception {
        HttpGet getRequest = new HttpGet(url.trim());
        return getHttpResultMsg(url, header, getRequest, encoding);
    }

    /**
     * @Description PUT 请求
     * @param url
     * @param postData
     * @param header
     * @param encoding
     * @return
     * @throws Exception
     */
    public static String doPutRequest(String url, String postData, Map<String, String> header, String encoding)
            throws Exception {
        HttpPut putRequest = new HttpPut(url.trim());
        //BODY
        StringEntity stringEntity = new StringEntity(postData, encoding);
        stringEntity.setContentType(HTTP_DEFAULT_CONTENT_TYPE);
        putRequest.setEntity(stringEntity);
        return getHttpResultMsg(url, header, putRequest, encoding);
    }

    /**
     * @param header
     * @param encoding
     * @return
     * @throws Exception
     */
    public static String doDeleteRequest(String url, Map<String, String> header, String encoding) throws Exception {
        //HTTP
        HttpDelete delRequest = new HttpDelete(url.trim());
        return getHttpResultMsg(url, header, delRequest, encoding);

    }

    /**
     * @param url
     * @param header
     * @param reqData
     * @param encoding
     * @return
     * @throws Exception
     */
    private static String getHttpResultMsg(final String url, Map<String, String> header, HttpRequestBase reqData,
                                           final String encoding)
            throws Exception {
        //HEADER
        if (header != null) {
            Set<Entry<String, String>> entrySet = header.entrySet();
            for (Iterator<Entry<String, String>> itor = entrySet.iterator(); itor.hasNext();) {
                Entry<String, String> entry = (Entry<String, String>) itor.next();
                reqData.setHeader(entry.getKey(), entry.getValue());
            }
        }
        //REQ&ANS
        //使用hander直接让其得到InputStream并关闭流及释放连接操作，应用层不用关心
        //具体看源码 CloseableHttpClient类  216行代码
        String responseString = getClient().execute(reqData, new ResponseHandler<String>() {
            public String handleResponse(HttpResponse response) {
                String result = StringUtils.EMPTY;
                HttpEntity entity = response.getEntity();
                int statueCode = response.getStatusLine().getStatusCode();
                try {
                    result = EntityUtils.toString(entity, encoding);
                    if (statueCode != 200) {
                        System.out.println("http返回异常:"+result);
                        return APIGW_HTTP_EXCEPTION_HEAD + statueCode;
                    }
                } catch (Exception e) {
                    System.out.println("得到应答数据异常url="+e);
                    return APIGW_HTTP_EXCEPTION_HEAD + statueCode;
                }
                return result;
            }
        });
        return responseString;
    }
}
